{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c5cc94d4",
   "metadata": {},
   "source": [
    "## 1. api文档\n",
    "https://reference.langchain.com/python/langchain_core/prompts/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "15907fc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.prompts import ChatPromptTemplate"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78717b51",
   "metadata": {},
   "source": [
    "# 2. 复习python的format"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "79d8fdbc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name: 小明, age: 25\n"
     ]
    }
   ],
   "source": [
    "# 位置参数\n",
    "info0 = \"name: {}, age: {}\".format(\"小明\", 25)\n",
    "print(info0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "bc32d2c1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'name: 小明, age: 25'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 关键字参数的\n",
    "info1 = \"name: {name}, age: {age}\".format(name=\"小明\", age=25)\n",
    "info1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "eafe9fa5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'name: 小明, age: 25'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 使用python的解包 **\n",
    "info_dict = {\"name\": \"小明\", \"age\": 25}\n",
    "info2 = \"name: {name}, age: {age}\".format(**info_dict)\n",
    "info2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "846984b1",
   "metadata": {},
   "source": [
    "# 3. 实例化PromptTemplate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "f4bf2040",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_variables=['product_info'] input_types={} partial_variables={} template='请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}'\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "template_str = \"请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}\"\n",
    "template = PromptTemplate(\n",
    "    template=template_str,\n",
    "    input_variables=[\"product_info\"], # 这里要跟template_str里的变量要一致\n",
    ")\n",
    "print(template)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c5ccb1a",
   "metadata": {},
   "source": [
    "# 4. 使用format方法把实例进行填充，得到模板"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "5089b538",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prompt_1:  请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：电脑\n",
      "prompt_2:  请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：电冰箱\n"
     ]
    }
   ],
   "source": [
    "# 通过模板构造提示词\n",
    "prompt_1 = template.format(product_info=\"电脑\")\n",
    "print(\"prompt_1: \", prompt_1)\n",
    "prompt_2 = template.format(product_info=\"电冰箱\")\n",
    "print(\"prompt_2: \", prompt_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "ff705c5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模板： input_variables=['product_info', 'requires', 'user_question'] input_types={} partial_variables={} template='请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}， 用户问题：{user_question}要求：{requires}'\n"
     ]
    }
   ],
   "source": [
    "# 举例多变量的\n",
    "from langchain_core.prompts import  PromptTemplate\n",
    "template_str = \"请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}， 用户问题：{user_question}要求：{requires}\"\n",
    "template = PromptTemplate(\n",
    "    template=template_str,\n",
    "    input_variables=[\"product_info\", \"user_question\", \"requires\"], # 这里要跟template_str里的变量要一致\n",
    ")\n",
    "print(\"模板：\", template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "c021807a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：XX品牌无线耳机：续航30小时，支持主动降噪，IP54防水，兼容安卓和iOS系统，售价299元， 用户问题：这款耳机能在跑步时用吗？要求：1. 重点说明防水性能是否适合运动场景；2. 用口语化表达，避免专业术语；3. 回答不超过3句话\n"
     ]
    }
   ],
   "source": [
    "filled_prompt = template.format(\n",
    "    product_info=\"XX品牌无线耳机：续航30小时，支持主动降噪，IP54防水，兼容安卓和iOS系统，售价299元\",\n",
    "    user_question=\"这款耳机能在跑步时用吗？\",\n",
    "    requires=\"1. 重点说明防水性能是否适合运动场景；2. 用口语化表达，避免专业术语；3. 回答不超过3句话\"\n",
    ")\n",
    "\n",
    "# 打印填充后的完整提示词\n",
    "print(filled_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "2abc16a1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "请你将以下文本总结为300字以内的内容。\n",
      "待总结文本：（此处替换为需要总结的长文本）\n",
      "要求：\n",
      "1. 涵盖文本核心观点，不遗漏关键信息；\n",
      "2. 语言简洁，符合300字以内的字数要求；\n",
      "3. 不添加个人观点或额外解释。\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# 举例场景\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "template = \"\"\"\n",
    "请你将以下文本总结为{summary_length}的内容。\n",
    "待总结文本：{text}\n",
    "要求：\n",
    "1. 涵盖文本核心观点，不遗漏关键信息；\n",
    "2. 语言简洁，符合{summary_length}的字数要求；\n",
    "3. 不添加个人观点或额外解释。\n",
    "\"\"\"\n",
    "\n",
    "summary_prompt = PromptTemplate(\n",
    "    input_variables=[\"text\", \"summary_length\"],  # 变量：待总结文本、总结长度\n",
    "    template=template\n",
    ")\n",
    "\n",
    "# 调用示例\n",
    "result = summary_prompt.format(\n",
    "    text=\"（此处替换为需要总结的长文本）\",\n",
    "    summary_length=\"300字以内\"\n",
    ")\n",
    "# 构建模板后，我只要改text，和summary_length\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "15d6015e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模板： input_variables=['product_info', 'requires', 'user_question'] input_types={} partial_variables={} template='请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}， 用户问题：{user_question}要求：{requires}'\n"
     ]
    }
   ],
   "source": [
    "# 使用from_template类方法\n",
    "from langchain_core.prompts import  PromptTemplate\n",
    "template_str = \"请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：{product_info}， 用户问题：{user_question}要求：{requires}\"\n",
    "template = PromptTemplate.from_template(\n",
    "    template=template_str,\n",
    "    # input_variables=[\"product_info\", \"user_question\", \"requires\"], # 注意注意，这里没有input_variables的变量啦\n",
    ")\n",
    "print(\"模板：\", template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "945c74e4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'请你作为产品顾问，根据以下产品信息，回答用户的问题。产品信息：XX品牌无线耳机：续航30小时，支持主动降噪，IP54防水，兼容安卓和iOS系统，售价299元， 用户问题：这款耳机能在跑步时用吗？要求：1. 重点说明防水性能是否适合运动场景；2. 用口语化表达，避免专业术语；3. 回答不超过3句话'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 这个时候就是用到了format了\n",
    "template.format(\n",
    "    product_info=\"XX品牌无线耳机：续航30小时，支持主动降噪，IP54防水，兼容安卓和iOS系统，售价299元\",\n",
    "    user_question=\"这款耳机能在跑步时用吗？\",\n",
    "    requires=\"1. 重点说明防水性能是否适合运动场景；2. 用口语化表达，避免专业术语；3. 回答不超过3句话\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dd0a6d0",
   "metadata": {},
   "source": [
    "# 5. partial_variables变量填充\n",
    "使用partial_variables变量填充，相当于函数的变量先设置默认值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "51817452",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始的prompt_template:  您好！欢迎使用智能助手服务, 我是订单助手。我想查询昨天的订单物流\n",
      "\n",
      "===使用partial_variables预先填充===\n",
      "\n",
      "使用输出1：\n",
      "您好！欢迎使用智能助手服务, 我是订单助手。我想查询昨天的订单物流\n",
      "--------------------------------------------------\n",
      "输出2：\n",
      "您好！欢迎使用智能助手服务, 我是售后客服。我的商品收到时有破损，能退换吗？\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import  PromptTemplate\n",
    "# 定义模板：包含固定前缀（用partial_variables预填充）和动态变量\n",
    "template_str=\"{greeting_prefix}, 我是{bot_name}。{user_message}\"  # 模板字符串，越通用越好\n",
    "\n",
    "template = PromptTemplate(\n",
    "    template=template_str,\n",
    "    input_variables=[\"greeting_prefix\", \"bot_name\", \"user_message\"]\n",
    ")\n",
    "\n",
    "prompt_template = template.format(\n",
    "    greeting_prefix=\"您好！欢迎使用智能助手服务\",\n",
    "    bot_name=\"订单助手\",\n",
    "    user_message=\"我想查询昨天的订单物流\",\n",
    ")\n",
    "print(\"原始的prompt_template: \", prompt_template)\n",
    "\n",
    "template = PromptTemplate(\n",
    "    template=template_str, # 不改变template_str\n",
    "    input_variables=[\"bot_name\", \"user_message\"],  # 需要动态传入的变量\n",
    "    partial_variables={\n",
    "        \"greeting_prefix\": \"您好！欢迎使用智能助手服务\"  # 固定前缀，提前填充\n",
    "    }\n",
    ")\n",
    "# 先填充partial_variables，留着input_variables的变量进行format\n",
    "\n",
    "print(\"\\n===使用partial_variables预先填充===\\n\")\n",
    "# 1. 第一次调用：传入不同的机器人名称bot_name 和用户消息user_message\n",
    "prompt1 = template.format(\n",
    "    bot_name=\"订单助手\",\n",
    "    user_message=\"我想查询昨天的订单物流\"\n",
    ")\n",
    "print(\"使用输出1：\")\n",
    "print(prompt1)\n",
    "print(\"-\"*50)  # 分隔线\n",
    "\n",
    "# 2. 第二次调用：仅修改动态变量，固定前缀不变\n",
    "prompt2 = template.format(\n",
    "    bot_name=\"售后客服\",\n",
    "    user_message=\"我的商品收到时有破损，能退换吗？\"\n",
    ")\n",
    "print(\"输出2：\")\n",
    "print(prompt2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "f60acc87",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "下午好, 我是小助手。在吗？\n"
     ]
    }
   ],
   "source": [
    "# 用函数动态生成partial变量（比如根据时间切换问候语）\n",
    "def get_greeting_prefix():\n",
    "    from datetime import datetime\n",
    "    hour = datetime.now().hour\n",
    "    return \"早上好\" if 6 <= hour < 12 else \"下午好\" if 12 <= hour < 18 else \"晚上好\"\n",
    "\n",
    "# 动态前缀的模板\n",
    "template = PromptTemplate(\n",
    "    template=template_str, # template_str变量多，能使用更多的场景。\n",
    "    input_variables=[\"bot_name\", \"user_message\"],\n",
    "    partial_variables={\"greeting_prefix\": get_greeting_prefix()}  # 调用函数生成前缀，早上好，中午好，晚上好，不在是写死的。\n",
    ")\n",
    "\n",
    "# 不同时间运行会得到不同问候语\n",
    "print(template.format(bot_name=\"小助手\", user_message=\"在吗？\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc02d982",
   "metadata": {},
   "source": [
    "# 6. 使用partial方法先填充部分变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "aa84610a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "我吃了苹果\n",
      "我吃了米饭\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "template_str=\"我{action}了{thing}\"\n",
    "# 基础模板：有2个变量\n",
    "template = PromptTemplate(\n",
    "    template=template_str,\n",
    "    input_variables=[\"action\", \"thing\"]\n",
    ")\n",
    "\n",
    "# 用partial固定action为\"吃\"，只剩thing需要动态传\n",
    "partial_temp = template.partial(action=\"吃\")\n",
    "\n",
    "# 只需传thing即可\n",
    "print(partial_temp.format(thing=\"苹果\"))  # 输出：我吃了苹果\n",
    "print(partial_temp.format(thing=\"米饭\"))  # 输出：我吃了米饭"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "685c74a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "我吃了苹果\n",
      "我吃了米饭\n",
      "我吃了面条\n"
     ]
    }
   ],
   "source": [
    "# 对比下\n",
    "# 模板：我{action}了{thing}\n",
    "template = PromptTemplate(\n",
    "    template=template_str, \n",
    "    input_variables=[\"action\", \"thing\"]\n",
    ")\n",
    "\n",
    "# 每次都要写 action=\"吃\"（重复！麻烦！）\n",
    "print(template.format(action=\"吃\", thing=\"苹果\"))\n",
    "print(template.format(action=\"吃\", thing=\"米饭\"))\n",
    "print(template.format(action=\"吃\", thing=\"面条\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "28f3ff81",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "我吃了苹果\n",
      "我吃了米饭\n",
      "我吃了面条\n"
     ]
    }
   ],
   "source": [
    "# 固定 action=\"吃\"，后续只传 thing\n",
    "partial_temp = template.partial(action=\"吃\")\n",
    "\n",
    "# 不用再写 action=\"吃\"（简洁！不易错！）\n",
    "print(partial_temp.format(thing=\"苹果\"))\n",
    "print(partial_temp.format(thing=\"米饭\"))\n",
    "print(partial_temp.format(thing=\"面条\"))\n",
    "# 用多个变量占位，支持拓展性，用partial来减少重复的代码。就跟函数里的参数，有默认参数一样。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "9d7e9780",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 实际用处（3 个核心场景）\n",
    "# 减少重复输入：如果某个变量（比如 “平台名称”“角色”“动作”）在多次调用中都不变，用 partial() 固定后，不用每次都写。\n",
    "# 降低出错风险：重复输入容易写错（比如把 “系统公告” 写成 “系统通告”），固定一次就不会出错。\n",
    "# 简化模板使用：复杂模板可能有 5-10 个变量，固定大部分后，后续调用只需关注 1-2 个变化的变量，不用记所有参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63db697a",
   "metadata": {},
   "source": [
    "# 7. from_template和partial方法联合使用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "e8de6d37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你是一个小学数学老师，请用用生活化例子，避免公式风格讲解：\n",
      "知识点：为什么1+1=2？\n",
      "\n",
      "\n",
      "--------------------------------------------------\n",
      "\n",
      "你是一个小学数学老师，请用用生活化例子，避免公式风格讲解：\n",
      "知识点：如何理解平均分？\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "# 完整模板：包含角色、风格、知识点\n",
    "full_template = \"\"\"你是一个{role}，请用{style}风格讲解：\n",
    "知识点：{knowledge}\n",
    "\"\"\"\n",
    "\n",
    "# 预填充固定角色和风格\n",
    "partial_template = PromptTemplate.from_template(full_template).partial(\n",
    "    role=\"小学数学老师\",\n",
    "    style=\"用生活化例子，避免公式\"\n",
    ")\n",
    "\n",
    "# 只需传入不同知识点\n",
    "print(partial_template.format(knowledge=\"为什么1+1=2？\"))\n",
    "print(\"\\n\" + \"-\"*50 + \"\\n\")  # 分隔线\n",
    "print(partial_template.format(knowledge=\"如何理解平均分？\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "e0dd66ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你是一个小学数学老师，请用用生活化例子，避免公式风格讲解：\n",
      "知识点：为什么1+1=2？\n",
      "\n",
      "\n",
      "--------------------------------------------------\n",
      "\n",
      "你是一个语文老师，请用用生活化例子，避免公式风格讲解：\n",
      "知识点：为什么花儿这么红\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "# 完整模板：包含角色、风格、知识点\n",
    "full_template = \"\"\"你是一个{role}，请用{style}风格讲解：\n",
    "知识点：{knowledge}\n",
    "\"\"\"\n",
    "\n",
    "# 预填充固定角色和风格\n",
    "partial_template = PromptTemplate.from_template(full_template).partial(\n",
    "    style=\"用生活化例子，避免公式\"\n",
    ")\n",
    "# 这里langchain，一定要使用关键字参数进行传参\n",
    "# 先把中间的style填充。再format两边的role和knowledge也是可以的，因为使用了关键字传参。\n",
    "print(partial_template.format(role=\"小学数学老师\", knowledge=\"为什么1+1=2？\"))\n",
    "print(\"\\n\" + \"-\"*50 + \"\\n\")  # 分隔线\n",
    "print(partial_template.format(role=\"语文老师\", knowledge=\"为什么花儿这么红\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "08e8b2de",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "15\n",
      "21\n"
     ]
    }
   ],
   "source": [
    "# 简单原理解释\n",
    "# python的 partial\n",
    "from functools import partial\n",
    "def add(a, b, c):\n",
    "    return a + b + c\n",
    "# 固定 c=10，生成新函数（只需传 b 和 c）\n",
    "add_10 = partial(add, c=10)\n",
    "# 调用新函数：只需传 b 和 c\n",
    "print(add_10(a=2, b=3))  # 10 + 2 + 3 = 15\n",
    "print(add_10(5, 6))      # 10 + 5 + 6 = 21（位置参数也支持）, 但是这里特意固定的是c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "bf8b878e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "21\n"
     ]
    }
   ],
   "source": [
    "add_a = partial(add, a=10)\n",
    "print(add_a(b=2, c=9))\n",
    "# print(add_a(2, 9)) # 2,填给那个变量呢？9填给那个变量呢？\n",
    "# print(add(a=10, 2, 9)) # 肯定报错"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "ce1b112f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prompt_1 小明在教室做写作业。\n",
      "prompt_2 老师在教室做讲课。\n"
     ]
    }
   ],
   "source": [
    "# 使用partil_variables变量传参，然后使用format\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "prompt_template = PromptTemplate.from_template(\n",
    "    template=\"{person}在{place}做{thing}。\",\n",
    "    partial_variables={\"place\": \"教室\"}, # 固定地点是“教室”\n",
    ")\n",
    "prompt_1 = prompt_template.format(person=\"小明\", thing=\"写作业\")\n",
    "print(\"prompt_1\", prompt_1)\n",
    "prompt_2 = prompt_template.format(person=\"老师\", thing=\"讲课\")\n",
    "print(\"prompt_2\", prompt_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ac37e1e",
   "metadata": {},
   "source": [
    "# 8. 使用\"+\"进行模板拼接\n",
    "原理是重载了 \"+\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "5cd8e9af",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请分析无线蓝牙耳机的降噪功能，说明其优势和不足（各列2点）\n",
      "最后用一句话总结核心结论\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "# 基础模板：定义核心任务\n",
    "base_template = PromptTemplate.from_template(\n",
    "    \"请分析{product}的{feature}，说明其\"\n",
    ")\n",
    "# 拼接细节要求和格式约束\n",
    "full_template = (\n",
    "    base_template\n",
    "    + \"优势和不足（各列2点）\"  # 补充分析维度\n",
    "    + \"\\n最后用{format}总结核心结论\"  # 补充输出格式\n",
    ")\n",
    "\n",
    "# 填充变量生成完整提示词\n",
    "prompt = full_template.format(\n",
    "    product=\"无线蓝牙耳机\",\n",
    "    feature=\"降噪功能\",\n",
    "    format=\"一句话\"\n",
    ")\n",
    "\n",
    "print(prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94a740ca",
   "metadata": {},
   "source": [
    "# 9. 使用 from_examples类方法构造模板"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "50ed5c8a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你是一个**小学数学老师**，请用**用小朋友熟悉的零食、文具举例子，不说公式**风格讲解乘法：\n",
      "\n",
      "讲解“2×3”：你每天吃2颗草莓，连续吃3天，一共吃了多少颗呀？第一天2颗，第二天2颗，第三天2颗，加起来是6颗，所以2乘3等于6～\n",
      "\n",
      "讲解“4×2”：每个小朋友分4块橡皮，分给2个小朋友，一共要准备多少块呀？第一个小朋友4块，第二个小朋友4块，合起来是8块哦～\n",
      "\n",
      "请用同样的方式讲解：**3×5**\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "# 定义示例（生活化的乘法讲解案例）\n",
    "multiplication_examples = [\n",
    "    \"讲解“2×3”：你每天吃2颗草莓，连续吃3天，一共吃了多少颗呀？第一天2颗，第二天2颗，第三天2颗，加起来是6颗，所以2乘3等于6～\",\n",
    "    \"讲解“4×2”：每个小朋友分4块橡皮，分给2个小朋友，一共要准备多少块呀？第一个小朋友4块，第二个小朋友4块，合起来是8块哦～\"\n",
    "]\n",
    "\n",
    "# 用 from_examples 生成提示模板\n",
    "# 这里的参数顺序我按照提示词的顺序。\n",
    "prompt = PromptTemplate.from_examples(\n",
    "    prefix=\"你是一个**{role}**，请用**{style}**风格讲解乘法：\",  # 前缀角色和风格定义\n",
    "    examples=multiplication_examples,  # 参考的讲解例子\n",
    "    suffix=\"请用同样的方式讲解：**{num1}×{num2}**\",  # 要讲解的新内容（带变量）\n",
    "    input_variables=[\"num1\", \"num2\", \"role\", \"style\"],  # 模板中需要填充的变量\n",
    "    example_separator=\"\\n\\n\"  # 例子之间的分隔符\n",
    ")\n",
    "\n",
    "# 填充变量并打印提示内容\n",
    "print(prompt.format(\n",
    "    role=\"小学数学老师\",\n",
    "    style=\"用小朋友熟悉的零食、文具举例子，不说公式\",\n",
    "    num1=3,\n",
    "    num2=5\n",
    "))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3664afa4",
   "metadata": {},
   "source": [
    "# 10. from_file读取文件构造模板\n",
    "场景，有时候我们把提示词放到txt文档中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "0ba9c113",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你是一个**小学数学老师**，请用**用小朋友熟悉的零食、文具举例子，不说公式**风格讲解乘法：\n",
      "参考以下例子的讲解思路：\n",
      "讲解“2×3”：你每天吃2颗草莓，连续吃3天，一共吃了多少颗呀？第一天2颗，第二天2颗，第三天2颗，加起来是6颗，所以2乘3等于6～\n",
      "讲解“4×2”：每个小朋友分4块橡皮，分给2个小朋友，一共要准备多少块呀？第一个小朋友4块，第二个小朋友4块，合起来是8块哦～\n",
      "请用同样的方式讲解：**3×5**\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "prompt_template = PromptTemplate.from_file(\n",
    "    template_file=\"template_sample.txt\",\n",
    "\n",
    ")\n",
    "prompt = prompt_template.format(\n",
    "    role=\"小学数学老师\",\n",
    "    style=\"用小朋友熟悉的零食、文具举例子，不说公式\",\n",
    "    # examples=multiplication_examples,\n",
    "    examples=\"\\n\".join(multiplication_examples), # 用上面的例子\n",
    "    num1=3,\n",
    "    num2=5\n",
    ")\n",
    "print(prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1e5d7aa",
   "metadata": {},
   "source": [
    "# 11. 使用invoke进行编排\n",
    "所有实现了 RunnableSerializable 接口的对象，都能调用 invoke() 方法，以此替代之前的 format() 调用方式。\n",
    "\n",
    "两者的区别在于返回值类型：\n",
    "\n",
    "format() 直接返回字符串；\n",
    "\n",
    "invoke() 返回 PromptValue 类型对象，若需要字符串，可再调用其 to_string() 方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "f4b1e873",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prompt_invoke_value text='描述一只松鼠在树上藏坚果的场景，要求生动形象。'\n",
      "invoke()返回类型： <class 'langchain_core.prompt_values.StringPromptValue'>\n",
      "\n",
      "最终提示词：\n",
      "描述一只松鼠在树上藏坚果的场景，要求生动形象。\n",
      "--------------------------------------------------\n",
      "正常format后的类型:  <class 'str'>\n",
      "正常format后:  描述一只松鼠在树上藏坚果的场景，要求生动形象。\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "# 1. 用初始化器创建PromptTemplate实例（实现了RunnableSerializable接口）\n",
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"animal\", \"action\"],\n",
    "    template=\"描述一只{animal}在{action}的场景，要求生动形象。\"\n",
    ")\n",
    "\n",
    "# 2. 使用invoke()调用（传入字典参数）\n",
    "# 返回值是PromptValue类型，包含生成的提示词\n",
    "prompt_invoke_value = prompt.invoke({\"animal\": \"松鼠\", \"action\": \"树上藏坚果\"})\n",
    "print(\"prompt_invoke_value\", prompt_invoke_value)\n",
    "\n",
    "# 3. 查看返回类型\n",
    "print(\"invoke()返回类型：\", type(prompt_invoke_value))  # 输出： <class 'langchain_core.prompt_values.StringPromptValue'>\n",
    "\n",
    "# 4. 转换为字符串（通过to_string()方法）\n",
    "result = prompt_invoke_value.to_string()\n",
    "print(\"\\n最终提示词：\")\n",
    "print(result)  # 输出：描述一只松鼠在树上藏坚果的场景，要求生动形象。\n",
    "\n",
    "# 5. 使用正常的format\n",
    "print(\"-\"*50)\n",
    "prompt_format = prompt.format(\n",
    "    **{\"animal\": \"松鼠\", \"action\": \"树上藏坚果\"}\n",
    ")\n",
    "print(\"正常format后的类型: \", type(prompt_format))\n",
    "print(\"正常format后: \", prompt_format)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "fe5dd34b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "invoke()返回类型： <class 'langchain_core.prompt_values.StringPromptValue'>\n",
      "prompt_invoke_value:  text='请分析无线蓝牙耳机的降噪功能，说明其优势和不足（各列2点）\\n最后用一句话总结核心结论'\n",
      "prompt_invoke_str返回类型:  <class 'str'>\n",
      "prompt_invoke_str: 请分析无线蓝牙耳机的降噪功能，说明其优势和不足（各列2点）\n",
      "最后用一句话总结核心结论\n"
     ]
    }
   ],
   "source": [
    "# 基础模板：定义核心任务\n",
    "base_template = PromptTemplate.from_template(\n",
    "    \"请分析{product}的{feature}，说明其\"\n",
    ")\n",
    "# 拼接细节要求和格式约束\n",
    "full_template = (\n",
    "    base_template\n",
    "    + \"优势和不足（各列2点）\"  # 补充分析维度\n",
    "    + \"\\n最后用{format}总结核心结论\"  # 补充输出格式\n",
    ")\n",
    "\n",
    "# 填充变量生成完整提示词\n",
    "prompt_invoke_value = full_template.invoke(\n",
    "    {\n",
    "        \"product\": \"无线蓝牙耳机\",\n",
    "        \"feature\": \"降噪功能\",\n",
    "        \"format\": \"一句话\"\n",
    "    }\n",
    ")\n",
    "print(\"invoke()返回类型：\", type(prompt_invoke_value))\n",
    "print(\"prompt_invoke_value: \", prompt_invoke_value)\n",
    "prompt_invoke_str = prompt_invoke_value.to_string()\n",
    "print(\"prompt_invoke_str返回类型: \", type(prompt_invoke_str))\n",
    "print(\"prompt_invoke_str:\", prompt_invoke_str)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "264ace6b",
   "metadata": {},
   "source": [
    "# 12. 结合大模型使用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "90ad4f44",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "# 把key配置到根目录下\n",
    "from dotenv import load_dotenv\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "480339eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "base_url: https://dashscope.aliyuncs.com/compatible-mode/v1\n",
      "api_key: 35\n",
      "model_name: qwen-plus\n"
     ]
    }
   ],
   "source": [
    "base_url = os.getenv(\"DASHSCOPE_BASE_URL\")\n",
    "api_key = os.getenv(\"DASHSCOPE_API_KEY\")\n",
    "model_name = os.getenv(\"DASHSCOPE_MODEL_NAME\")\n",
    "print(\"base_url:\", base_url)\n",
    "print(\"api_key:\", len(api_key))\n",
    "print(\"model_name:\", model_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "cf995220",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prompt 作为测评博主，对比迷你加湿器的加湿效果和噪音控制，客观说优缺点。在100字以内\n",
      "type(prompt): <class 'str'>\n"
     ]
    }
   ],
   "source": [
    "# 创建模板\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "prompt_template = PromptTemplate.from_template(\n",
    "    template=\"作为测评博主，对比{product}的{aspect1}和{aspect2}，客观说优缺点。在100字以内\"\n",
    ")\n",
    "# 在100字以内用来限制下输出，不然很长，也要等很久\n",
    "prompt = prompt_template.format(\n",
    "    product=\"迷你加湿器\", aspect1=\"加湿效果\", aspect2=\"噪音控制\"\n",
    ")\n",
    "print(\"prompt\", prompt)\n",
    "print(\"type(prompt):\", type(prompt))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10f61b49",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 994 ms, sys: 416 ms, total: 1.41 s\n",
      "Wall time: 9.83 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "AIMessage(content='迷你加湿器便携省空间，加湿效果适中，适合小范围使用；但水箱小需频繁加水，加湿时长有限。多数机型运行安静，噪音低于30分贝，适合办公或睡眠使用。缺点是部分低价产品雾化不均，易产生噪音波动。整体性价比高，但性能弱于大型加湿器。', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'qwen-plus', 'model_provider': 'openai'}, id='lc_run--60f31bd6-9dff-486e-be88-a860fbb4892a')"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%time\n",
    "# 创建大模型\n",
    "from langchain_openai import ChatOpenAI\n",
    "llm = ChatOpenAI(\n",
    "    model_name=model_name,\n",
    "    openai_api_key=api_key,\n",
    "    openai_api_base=base_url,\n",
    "    streaming=True,\n",
    ")\n",
    "llm.invoke(prompt)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-course (3.10.12)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
